#include "qtsignalcatcher.h"

#include <private/qobject_p.h>

#include <QSet>
#include <QMetaMethod>

class QtSignalCatcherBasePrivate : public QObjectPrivate
{
public:
    QtSignalCatcherBasePrivate();

public:
    QMap<const QObject *, QSet<const QMetaObject *>> metaObjects;
    static const int overflow;
};

const int QtSignalCatcherBasePrivate::overflow = QtSignalCatcherBase::staticMetaObject.methodCount();

QtSignalCatcherBasePrivate::QtSignalCatcherBasePrivate() : QObjectPrivate{}
{
}

QtSignalCatcherBase::QtSignalCatcherBase(QObject *parent) : QObject{*new QtSignalCatcherBasePrivate, parent}
{
}

void QtSignalCatcherBase::catchObject(const QObject *object, QList<const QMetaObject *> metaObjects)
{
    Q_D(QtSignalCatcherBase);
    QSet<const QMetaObject *> &mos = d->metaObjects[object];

    if (mos.empty())
        QMetaObject::connect(object, -1, this, d->overflow);

    if (metaObjects.empty()) {
        for (const QMetaObject *mo = object->metaObject(); mo; mo = mo->superClass())
            mos.insert(mo);
    } else {
        for (const QMetaObject *mo : metaObjects)
            mos.insert(mo);
    }
}

void QtSignalCatcherBase::releaseObject(const QObject *object, QList<const QMetaObject *> metaObjects)
{
    Q_D(QtSignalCatcherBase);
    if (!d->metaObjects.contains(object))
        return;

    QSet<const QMetaObject *> &mos = d->metaObjects[object];

    for (const QMetaObject *mo : metaObjects)
        mos.remove(mo);

    if (metaObjects.empty() || mos.empty())
        d->metaObjects.remove(object);

    if (!d->metaObjects.contains(object))
        QMetaObject::disconnect(object, -1, this, d->overflow);
}

void QtSignalCatcherBase::relay(void **argv)
{
    Q_D(QtSignalCatcherBase);
    QObject * const object = sender();
    const int index = senderSignalIndex();
    const QMetaMethod &signal = sender()->metaObject()->method(index);

    if (d->metaObjects.value(object).contains(signal.enclosingMetaObject()))
        Q_EMIT catched(object, signal, argv);
}

QtSignalCatcher::QtSignalCatcher(QObject *parent) : QtSignalCatcherBase{parent}
{
}

int QtSignalCatcher::qt_metacall(QMetaObject::Call call, int methodId, void **argv)
{
    methodId = QtSignalCatcherBase::qt_metacall(call, methodId, argv);
    if (methodId < 0)
        return methodId;

    if (call == QMetaObject::InvokeMetaMethod) {
        if (methodId == 0) {
            relay(argv);
        }
        --methodId;
    }
    return methodId;
}
